package com.zdyt.opc.config;

import com.zdyt.opc.common.DataSampAndSender;
import com.zdyt.opc.common.ItemState;
import com.zdyt.opc.common.TaskSartSateListener;
import com.zdyt.opc.common.TaskStopStateListener;
import com.zdyt.opc.entity.Item;
import com.zdyt.opc.entity.SampAccessTask;
import lombok.extern.slf4j.Slf4j;
import org.jinterop.dcom.common.JIException;
import org.openscada.opc.dcom.list.ClassDetails;
import org.openscada.opc.lib.common.ConnectionInformation;
import org.openscada.opc.lib.common.NotConnectedException;
import org.openscada.opc.lib.da.*;
import org.openscada.opc.lib.da.browser.FlatBrowser;
import org.openscada.opc.lib.list.Categories;
import org.openscada.opc.lib.list.Category;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Slf4j
public class OpcOperation extends OpcClient {


//    private Map<Integer,List<String>> access2ItemMap = new ConcurrentHashMap<>();  // 正在采集的Item集合

    private List<String> sampingItems = new CopyOnWriteArrayList<>(); //所有正在采集得任务

//    private List<String> allItems = new CopyOnWriteArrayList<>(); // 所有在任务中得采集点

    private Map<String/*itemID*/,Integer/*taskID*/> items2Task = new ConcurrentHashMap<>(); // 在任务中的采集点


    private Map<Integer,SampAccessTask> allAccessMap = new ConcurrentHashMap<>();  // 记录不同时间周期的采集任务

    private Map<String,List<Integer>> serverAccessMap = new ConcurrentHashMap<>(); // 记录不同Server下得采集任务

    private int defaultPeriod;  //默认为10s

    private static AtomicInteger accessCount = new AtomicInteger();

    private static int DEFAULT_WAIT_TIME = 5; //5s

    private String defaultTargetUrl;


    public OpcOperation(OpcProperties opcProperties) {
        super(opcProperties.getHost(),opcProperties.getUser(),opcProperties.getPassword(),opcProperties.getDomain(),opcProperties.getProgId());
        this.defaultPeriod = opcProperties.getPeriod();
        this.defaultTargetUrl = opcProperties.getTargetUrl();
    }


    // ============== 采集任务相关==============
    /**
     *  获取目标OPCServer下得所有任务
     */
    public List<SampAccessTask> getTagertServerAccessTasks(String progId){
        if (StringUtils.isEmpty(progId)){
            progId = getProgId();
        }
        List<SampAccessTask> tasks = new ArrayList<>();

        List<Integer> accessIds = serverAccessMap.get(progId);
        if (accessIds != null){
            tasks = accessIds.stream().map((id) -> allAccessMap.get(id)).collect(Collectors.toList());
        }
        return tasks;
    }

    /**
     *  创建 采集任务
     */
    public SampAccessTask createSampAccessTask(String taskName,Integer period,String targetUrl){

        if (period == null || period < defaultPeriod){
            period = defaultPeriod;
        }

        if (!isConnected()){
            throw new RuntimeException("请先连接OPCServer后，再来创建采集任务！");
        }
        try {
            AccessBase accessBase = new SyncAccess(getmServer(), period);
            int accessId = accessCount.incrementAndGet();
            if (targetUrl == null || "".equals(targetUrl)){
                targetUrl = defaultTargetUrl;
            }

            SampAccessTask task = new SampAccessTask(taskName, accessId, period, accessBase,targetUrl);
            allAccessMap.put(accessId,task);
            List<Integer> ids = serverAccessMap.computeIfAbsent(getProgId(), (progId) -> new ArrayList<>());
            ids.add(accessId);
            return task;
        } catch (UnknownHostException | NotConnectedException | JIException | DuplicateGroupException e) {
            log.error(e.getMessage());
            throw new RuntimeException("创建采集任务失败,请检查相关配置是否正确！");
        }
    }

    /**
     *  启动采集任务
     * @param accessId 采集任务Id
     * @return  采集任务
     */
    public SampAccessTask startSampAccessTaskById(Integer accessId){

        // 对采集任务做判断
        if (accessId == null){
            throw new RuntimeException("没有指定采集任务Id");
        }

        SampAccessTask task = allAccessMap.get(accessId);
        if (task == null){
            throw new RuntimeException("采集任务不存在!");
        }

        if (task.isBound()){
            throw new RuntimeException("该任务已经启动,请勿重复启动！");
        }

        // 创建启动监听器，监听任务启动状态
        CountDownLatch countDownLatch = new CountDownLatch(1);
        TaskSartSateListener listener = new TaskSartSateListener(countDownLatch);
        task.addStateListener(listener);

        // 启动任务
        task.bind();
        try {
            countDownLatch.await(DEFAULT_WAIT_TIME,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 移除启动监听器
            task.removeStateListener(listener);
        }
        // 添加到正在采集 采集点集合中
        sampingItems.addAll(task.getItemIds());
        log.info("启动任务【{}】成功",task.getTaskId());
        return task;
    }

    /**
     *  停止采集任务
     * @param accessId 采集任务ID
     * @return 采集任务
     */
    public SampAccessTask stopSampAccessTaskById(Integer accessId) {
        // 对采集任务做判断
        if (accessId == null){
            throw new RuntimeException("没有指定采集任务Id");
        }

        SampAccessTask task = allAccessMap.get(accessId);
        if (task == null){
            throw new RuntimeException("采集任务不存在!");
        }

        if (!task.isBound()){
            return task;
        }

        // 添加任务 关闭监听器
        CountDownLatch countDownLatch = new CountDownLatch(1);
        TaskStopStateListener listener = new TaskStopStateListener(countDownLatch);
        task.addStateListener(listener);

        try {
            // 关闭任务
            task.unbind();
            countDownLatch.await(DEFAULT_WAIT_TIME,TimeUnit.SECONDS);
        } catch (JIException | InterruptedException e) {
            log.error(e.getMessage());
        }finally {
            // 移除任务关闭监听器
            task.removeStateListener(listener);
        }

        // 从 所有正在采集点中 移除该任务得采集点
        sampingItems.removeAll(task.getItemIds());
        return task;
    }

    /**
     * 删除采集任务
     * @param accessId 采集任务ID
     */
    public void deleteSampAccessTaskById(Integer accessId) {
        // 1. 先停止采集任务
        SampAccessTask task = stopSampAccessTaskById(accessId);

        // 2. 从items2Task中 移除在当前任务中得 采集点
        List<String> itemIds = task.getItemIds();
        if (itemIds.size() > 0){
            for (String itemId : itemIds) {
                items2Task.remove(itemId);
            }
        }

        // 3. 移除allAccessMap
        allAccessMap.remove(task.getTaskId());

        // 4. 移除serverAccessMap
        serverAccessMap.computeIfPresent(getProgId(), (progId, taskIds) -> {
            taskIds.remove(task.getTaskId());
            return taskIds;
        });
    }


    // ================ 采集点相关=============

    /**
     *  添加采集点 到采集任务中
     * @param itemIds   采集点集合
     */
    public SampAccessTask addSampPointToTask(List<String> itemIds, Integer accessId, DataCallback callback){
        if (accessId == null){
            throw new RuntimeException("目标任务不存在,请先创建任务后再添加采集点！");
        }
        SampAccessTask task = allAccessMap.get(accessId);
        if (task == null){
            throw new RuntimeException("目标任务不存在,请先创建任务后再添加采集点！");
        }

        if ( itemIds == null || itemIds.size() <= 0){
            throw new RuntimeException("目标采集点是空,请检查需要采集点是否存在！");
        }

        for (String itemId : itemIds) {
            try {
                // 跳过已存在得itemId, 也就是已经处于当前任务，或者其它任务中得ItemId不进行添加
                if (items2Task.get(itemId)!= null){
                    continue;
                }
                task.addItems(itemId, callback);
                items2Task.put(itemId, accessId);
            } catch (AddFailedException | JIException e) {
                throw new RuntimeException("采集点添加失败！cause:【" + e.getMessage() + "】");
            }
        }

        return task;
    }

    /**
     *  从采集任务中 删除采集点
     * @param itemIds  要删除的采集点id集合
     * @param accessId  采集任务
     */
    public void deleteSampItemFromTask(List<String> itemIds, Integer accessId) {
        if (accessId == null){
            throw new RuntimeException("目标任务不存在,请先创建任务后再添加采集点！");
        }
        SampAccessTask task = allAccessMap.get(accessId);
        if (task == null){
            throw new RuntimeException("目标任务不存在,请先创建任务后再添加采集点！");
        }

        if ( itemIds == null || itemIds.size() <= 0){
            throw new RuntimeException("目标采集点是空,请检查需要采集点是否存在！");
        }

        for (String itemId : itemIds) {
            // 1.从任务中移除采集点
            task.removeItem(itemId);

            // 2.从items2Task
            items2Task.remove(itemId);

            // 3.从sampingItems
            if (task.isBound()){
                sampingItems.remove(itemId);
            }
        }
    }



    // 停止服务器之前 做一些清理操作
    public void disConnectServer(){
        clearCurrentServerAndStopAccessMap();
        super.disConnectServer();
    }

    // 清理任务操作
    private void clearCurrentServerAndStopAccessMap() {
        String progId = getCi().getProgId();
        List<Integer> accessIds = serverAccessMap.get(progId);

        if (accessIds!=null && accessIds.size()>0){
            // 停止任务，并清空任务中得items
            accessIds.forEach((accessId)->{

                // 1.清空 allAccessMap
                SampAccessTask task = allAccessMap.remove(accessId);
                try {
                    task.unbind();
                    task.clear();
                } catch (JIException e) {
                    e.printStackTrace();
                }

                // 2.清空当前Server得 items2Task ，sampingItems 所有采集点
                items2Task.clear();
                sampingItems.clear();
            });

            // 清空当前Server下 所有 access
            serverAccessMap.remove(progId);
        }

    }


    // 获取 目标OPCServer 中所有的 采集点Id
    public List<Item>getAllItemList(){

        if (!isConnected()){
            throw new RuntimeException("连接超时,请先检查基础连接配置是否正确！");
        }

        List<String> allItemList = new ArrayList<>();
        Server server = getmServer();
        FlatBrowser flatBrowser = server.getFlatBrowser();
        if (flatBrowser == null){
            throw new RuntimeException("连接异常,请先检查基础连接配置是否正确！");
        }

        try {
            Collection<String> browses = flatBrowser.browse();
            allItemList.addAll(browses);
        } catch (UnknownHostException | JIException e) {
            throw new RuntimeException("连接异常,请先检查基础连接配置是否正确！");
        }

        // 构造 item 集合
        List<Item> items = new ArrayList<>();
        if (allItemList.size() > 0){
            items = allItemList.stream().map((itemId)->{
                Item item = new Item();
                item.setItemId(itemId);
                Integer taskId = items2Task.get(itemId);
                if (taskId != null){
                    item.setTaskId(taskId);
                    if (sampingItems.contains(itemId)){
                        item.setState(ItemState.SAMPING_ITEM);
                    }else {
                        item.setState(ItemState.IN_TASK_ITEM);
                    }
                }else {
                    item.setState(ItemState.COMMON_ITEM);
                }
                return item;
            }).collect(Collectors.toList());
        }
        return items;
    }


    // 获取server上的OPC服务器应用列表
    public List<ClassDetails> getAllOPCServers() throws JIException, UnknownHostException {
        List<ClassDetails> classDetails = new ArrayList<>();
        if (isServerListConnected()){
            // 支持DA 1.0，DA 2.0规范
            Collection<ClassDetails> detailsList = getServerList().listServersWithDetails(
                    new Category[] { Categories.OPCDAServer10, Categories.OPCDAServer20,Categories.OPCDAServer30 }, new Category[] {});
            classDetails.addAll(detailsList);
        }

        return classDetails;
    }

    // 根据accessId 获取 accessTask
    public SampAccessTask getAccessTaskById(Integer accessId) {
        return allAccessMap.get(accessId);
    }


}